#!/usr/bin/perl
# ------------------------------------------------------------------
# Perl script to generate a "make"-style dependency list for a
# C, C++, or FORTRAN source file with CPP include directives.  
#
# Usage:  mk_dep -DBG [-I<dir>]* [-X<dir>]* filename ...
# Notes:  *  -I<path> defines a search path for include files
#         *  -DBG turn on debug flag
#         *  -D<anything_else> is ignored unless <anything_else> is in
#             avoidblock in which case it is removed.  avoidblock lists
#             names of #ifdef blocks from which includes are ignored
#             by default
#         *  -X<path> means disgard entries with this path (NOT IMPLEMENTED)
#         *  
#         *   searches current directory only if -I. is in search
#             path or #include directive uses double quotes rather
#             than angle brackets.
#         *   dependency list is sent to standard output
#         *   follows all #include directives, even those protected
#             by #if and #ifdef directives.
#         *   ignores include files not found in search path
#         *   Includes corresponding .C files for .H files including
#             template definitions
#         *   No duplications in dependency list
#
# Author:  Michael Welcome
#          4/26/95
#          Lawrence Livermore National Laboratory
#
# Modification History:
#  09Sep99 by D.Serafini: Force files named "*_F.H" to be output even
#          if they don't exist so chfpp will be called to make them.
#
# ------------------------------------------------------------------

$debug = 0;
@incldir = ("");
# This is a list, separated by '|', of #ifdef blocks to ignore includes from.
# Any that are defined on the command line are removed.  If all are removed,
# only _dumm_ remains which shouldn't match any #ifdef _dummy_
$avoidblock="CH_GPU|_dummy_";

# search command line for -I and -X options
while ($ARGV[0] =~ /^-/) {
    $_ = shift;
    if (/^-I(.+)/) {
        die "$1 does not exist\n" if (!-e $1);
        die "$1 not a directory\n" if (!-d $1);
        die "cannot read $1\n" if (!-r $1);
        push(@incldir,("$1/"));
    } elsif (/^-X(.+)/) {
        die "Sorry, -X option not implemented\n";
        #push(@excldir,($1));
    } elsif (/^-DBG$/) {
        $debug = 1;
    } elsif (/^-D(\S*)/) {
        # ignore cpp defines except those also in avoidblock which are removed
        $avoidblock =~ s/$1\|?//;
    } else {
#       die "invalid argument: $_\n";
    }
}

foreach $ifile (@ARGV) {

    print "PARSING FILE: @ARGV\n" if $debug;
    die "cannot read $ifile\n" if (!(-e $ifile && -r _));

    #define object file
    # strip path from filename
    ($ofile = $ARGV[0]) =~ s#.*/##;
    # change suffix to .o
    ($ofile = $ifile) =~ s/\.[^.\/]*$/.o/;
    @searchlist = ("$ifile\"");
    %usedfiles = ();
    %deptable = ();
    $usedfiles{ $ifile } = 1;

    while (@searchlist) {
        # get next file off search list
        $file = shift(@searchlist);

        # NOTE: the last char in $file is either a double quote (") or
        #       a right angle bracket (>).  Strip off this character and
        #       save it.  If it is a quote, search current directory first.
        #       If its a right angle bracket, only search directories
        #       specified with -I options.
        $incltype = chop $file;

        # NOTE: if the first char in $file is a "/" indicating an absolute
        #       path, do not search directory list
        $abspath = ($file =~ /^\// ? 1 : 0);

        # Check for special ChomboFortran prototype files
        # and replace them with the source file.  Later we'll
        # push the prototype file into the dependency list.
        # We do this in order to find the right directory
        # becuase the prototype file may not exist already.
        if ($file =~ /_F.H$/) {
            $file = $` . ".ChF" ;
            $FH_file = 1;
        } elsif ($file =~ /_CUX.H$/) {
            $file = $` . ".cu" ;
            $FH_file = 1;
        } else {
            $FH_file = 0 ;
        }

        $found = 0 ;
        foreach $d (@incldir) {
            if ($d ne "" && $abspath) {
                # this is an absolute path, dont search current directory
                next;
            }
            if ($d eq "" && $incltype eq ">") {
                # dont search current directory
                next;
            }
            $dep = "$d$file";
            print "Will search $d for $file :: $dep\n" if $debug;
            if (-e $dep) {

                $found = 1 ;

                print "$ofile: $dep\n" if $debug;

                # file found, see if it is a special ChomboFortran prototype file
                # and add the prototype filename to the dependency table
                if ( $FH_file == 1 && $file =~ /.ChF$/ ) {
                    $deptable{ $d . $` . "_F.H" } = 1;
                    last ;
                }
                if ( $FH_file == 1 && $file =~ /.cu$/ ) {
                    $deptable{ $d . $` . "_CUX.H" } = 1;
                    last ;
                }

                # otherwise, just build the dependency and enter in table
                $deptable{ $dep } = 1;

                # grep file for includes and, if not seen before, add
                # to end of search list
                open(FL,"$dep") || die "cant open $dep\n";
                # Avoid includes in #ifdef blocks marked by $avoidblock
                $avoidlvl=0;
                while (<FL>) {
                    if ($avoidlvl == 0) {
                        if (/^\s*#\s*include\s+["<]\s*([^">]+[">])/) {
                            if ($usedfiles{ $1 }++ == 0) {
                                print " ::: including ::$1::\n" if $debug;
                                # this file not searched yet, add to list
                                # NOTE: last char is either double quote
                                #       or right angle bracket.
                                push(@searchlist,($1));
                            }
                        }
                        elsif (/^\s*#\s*ifdef\s+(?:$avoidblock)/) {
                            $avoidlvl = 1;
                        }

                        if ($file =~ /.F$/ or $file =~ /.F90$/) {
                            if (/^\s*USE\s+([^ ,\n\r]*)/) {
                                $lmod = lc $1;
                                print " ::: USEing ::$lmod.mod::\n" if $debug;
                                print "$ofile: $lmod.mod\n";
                            }
                            if (/^\s*use\s+([^ ,\n\r]*)/) {
                                $lmod = lc $1;
                                print " ::: USEing ::$lmod.mod::\n" if $debug;
                                print "$ofile: $lmod.mod\n";
                            }
                        }
                    }
                    else {
                        if (/^\s*#\s*if/) {
                            ++$avoidlvl;
                        }
                        elsif (/^\s*#\s*endif/) {
                            --$avoidlvl;
                        }
                    }
                }
                
                # if this file is a header (.H) and it includes template
                # declarations, add the corresponcing .cpp file to the
                # searchlist.
                # Assume the .cpp file is in the same directory as the .H

#
#       Dec. 18/2000
# Brian thought that including the cpp for any file
# that has the word 'template' in it is really dumb. the only
# cpp file that should be in a .d file is the self-named cpp file.
# items which have definition scope beyond a single translation unit
# should ALWAYS be in a .H file.   g++ requires this anyways.
#               if ($file =~ /\S*\.[Hh]$/) {
#                   $Cfile = $file;
#                   $Cfile =~ s/\.H$/\.cpp/;
#                   print "Found Header file $dep\n" if $debug;
#                   print " . . Corresponding .cpp file is: $Cfile\n" if $debug;
#                   # position file pointer to beginning of file
#                   seek(FL,0,0);
#                   # search for template declarations
#                   while (<FL>) {
#                       # search for string "template" not in a comment
#                       s/\/\/.*//;
#                       if (/template/) {
#                           print "$dep contains template\n" if $debug;
#                           print "$_" if $debug;
#                           $inclCfile = $Cfile . $incltype;
#                           if ($usedfiles{ $inclCfile } == 0) {
#                               # print " ::: including ::$inclCfile::\n";
#                               # this file not searched yet, add to list
#                               # NOTE: last char is either double quote
#                               #       or right angle bracket.
#                               push(@searchlist,($inclCfile));
#                           }
#                           # mark as used file
#                           $usedfiles{ $inclCfile }++;
#                           # stop searching for tempate
#                           last;
#                       }
#                 }
#               }

                # since file was found in search path, jump out of loop
                last;
            }
        }

        # If the file is a ChomboFortran file, and it wasn't found in
        # any directory, make a dependency for the prototype file
        # in the current directory so it will be created.
        # Apr 2, 2013, SG - Created from what?  .ChF was not found! Why do this?
        if ($file =~ /.ChF$/ and $found == 0 and $incltype == '"' ) {
            $deptable{ "./" . $` . "_F.H" } = 1;
        }
#        if ($file =~ /.cu$/ and $found == 0 and $incltype == '"' ) {
#            $deptable{ "./" . $` . "_CUX.H" } = 1;
#        }

        print "@searchlist\n" if $debug;
    }

    # now generate dependency list
    for $dep (keys %deptable) {
        print "$ofile: $dep\n";
    }
}
